//
// Created by xzl on 2018/2/21.
//

#include "FtpUpdateor.h"
#include <sys/stat.h>
#include "Util/logger.h"
#include "Util/uv_errno.h"
#include "Thread/AsyncTaskThread.h"
#include "Poller/EventPoller.h"

using namespace ZL::Util;
using namespace ZL::Thread;
using namespace ZL::Poller;

FtpUpdateor::FtpUpdateor(const string &user_name,
                         const string &passwd) :
        FtpDownloader(user_name, passwd) {
}

FtpUpdateor::~FtpUpdateor() {
    cancel();
}

///////////事件/////////////
void FtpUpdateor::onStart(int total, int offset, int fileTime) {
    _callback.onStart(total, offset, fileTime);
}

void FtpUpdateor::onCancel() {
    _callback.onCancel();
}

void FtpUpdateor::onProgress(char *data, int size) {
    _callback.onProgress(data, size);
    if(_speedTicker.elapsedTime() > 1000){
        _speedTicker.resetTime();
        auto speed = downloadSpeed();
        if(speed > 1024){
            //InfoL << speed /1024 << " KB/s";
        }
    }

}

void FtpUpdateor::onComplete() {
    _callback.onComplete();
}

void FtpUpdateor::onFailed(int err_code, const string &err_msg) {
    _callback.onFailed(err_code, err_msg);
}


void FtpUpdateor::getRemoteFileInfo(const string &url,const onGetFileSize &cb) {
    if (!cb) {
        return;
    }

    //该回调是否已经触发，防止多次被触发
    std::shared_ptr<bool> calledFlag(new bool(false));
    auto cb_wrapper = [cb, calledFlag](int fileSize, int fileTime, int code, const string &errMsg) {
        if (!*calledFlag) {
            *calledFlag = true;
            cb(fileSize, fileTime, code, errMsg);
        }
    };
    //设置回调
    _callback.setCallBack(
            [this, cb_wrapper](int total, int offset, int fileTime) {
                //onStart
                this->cancel();
                cb_wrapper(total, fileTime, 0, "");
                return false;
            },
            [cb_wrapper]() {
                //onCancel
                cb_wrapper(-1, 0, -1, "operation canceled");
                return false;
            },
            nullptr,//onProgress
            nullptr,//onComplete
            [cb_wrapper](int err_code, const string &err_msg) {
                //onFailed
                cb_wrapper(-1, 0, err_code, err_msg);
                return false;
            });
    //开始下载
    if (!start(url)) {
        _callback.clear();
        cb_wrapper(-1, 0, -1, "start ftp failed,maybe resource is busy");
        return;
    }

}

void FtpUpdateor::update(const string &url,const string &filePath, const FtpUpdateor::onResult &cb) {
    if (!cb) {
        const_cast<onResult &>(cb) = onResult();
    }
    int fileSize = getFileSize(filePath);

    mINI fileIni;
    try {
        fileIni.parseFile(filePath + ".ini");
    }catch (...){
    }


    //本地文件日期
    long fileTime = fileIni[UPDATE_KEY_FILE_TIME];

    getRemoteFileInfo(url,[this, url,filePath, cb, fileSize, fileTime,fileIni](int remoteFileSize, int remoteFileTime, int err, const string &errMsg) {
        if (err != 0) {
            //获取文件大小失败
            cb(err, errMsg);
        } else if (remoteFileSize != fileSize || (remoteFileTime > 0 && fileTime != remoteFileTime)) {
            //文件大小不匹配或者日期不匹配
            auto cb_wrapper = [cb, filePath, remoteFileTime,fileIni, this](int code, const string &errMsg) {
                if (code ==0) {
                    //完整文件日期
                    const_cast<mINI &>(fileIni)[UPDATE_KEY_FILE_TIME] = remoteFileTime;
                    //临时文件日期,没有临时文件了
                    const_cast<mINI &>(fileIni)[UPDATE_KEY_FILE_TIME_TMP] = 0;
                    const_cast<mINI &>(fileIni).dumpFile(filePath + ".ini");
                }
                cb(code, errMsg);
            };

            bool resume = (const_cast<mINI &>(fileIni)[UPDATE_KEY_FILE_TIME_TMP] == remoteFileTime);
            //完整文件日期，强制更新完整文件
            const_cast<mINI &>(fileIni)[UPDATE_KEY_FILE_TIME] = 0;
            //临时文件日期
            const_cast<mINI &>(fileIni)[UPDATE_KEY_FILE_TIME_TMP] = remoteFileTime;
            const_cast<mINI &>(fileIni).dumpFile(filePath + ".ini");

            InfoL << "是否恢复下载:" << resume;
            //文件大小不匹配,需要更新
            ASYNC_TRACE([url,filePath, this, cb_wrapper,resume](){
                download(url,filePath, resume, cb_wrapper);
            });
        } else {
            //文件不用更新
            cb(-1, "file is up to date");
        }
    });

}

void FtpUpdateor::download(const string &url,const string &filePath, bool resume, const FtpUpdateor::onResult &cb) {
    if (!cb) {
        const_cast<onResult &>(cb) = onResult();
    }
    if (_file) {
        cb(-1, "file is downloading");
        return;
    }

    auto tmpFileName = filePath + ".tmp";
    auto tmpFile = File::createfile_file(tmpFileName.data(), resume ? "ab" : "wb");
    if (!tmpFile) {
        //打开文件失败，一般是没权限的原因
        cb(-1, string("fopen file failed:") + get_uv_errmsg());
        return;
    }
    int tmpFileSize = ftell(tmpFile);
    _file.reset(tmpFile, [](FILE *fp) {
        fclose(fp);
    });

    //设置回调
    _callback.setCallBack(
            nullptr, //onStart
            [cb,this]() {
                //onCancel
                _file.reset();
                cb(-1, "operation canceled");
                return false;
            },
            [this](char *data, int size) {
                //onProgress
                if (-1 == fwrite(data, size, 1, _file.get())) {
                    WarnL << "fwrite failed:" << get_uv_error();
                    cancel();
                    _file.reset();
                    return false;
                }
                return true;
            },
            [cb, this, filePath, tmpFileName]() {
                //onComplete
                _file.reset();//close file
                File::delete_file((filePath + ".trash").data());//删除trash文件
                rename(filePath.data(), (filePath + ".trash").data());//老文件标记删除
                //临时文件改名正式文件
                rename(tmpFileName.data(), filePath.data());//替换新文件
                cb(0, "success");
                return false;
            },
            [cb,tmpFileName,this](int err_code, const string &err_msg) {
                //onFailed
                if(err_code == CURLE_BAD_DOWNLOAD_RESUME){
                    //需要删除零时文件
                    _file.reset(File::createfile_file(tmpFileName.data(), "wb"),[](FILE *fp){
                        fclose(fp);
                    });
                }
                cb(err_code, err_msg);
                return false;
            });

    //断点续传
    if (!start(url,tmpFileSize)) {
        _file.reset();
        _callback.clear();
        cb(-1, "start ftp failed,maybe resource is busy");
        return;
    }
}

int FtpUpdateor::getFileSize(const string &filePath) {
    struct stat tFileStat;
    if (0 == stat(filePath.data(), &tFileStat)) {
        return tFileStat.st_size;
    }
    return -1;
}






